// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/***********************************************************************
 *
 * File: disasm.cpp
 *
 *  This file handles disassembly for the "late disassembler".
 *
 ***********************************************************************/

#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif

/*****************************************************************************/
#ifdef LATE_DISASM
/*****************************************************************************/

#ifdef USE_COREDISTOOLS
// The loggers have no way to pass a context variable, so this is a global.
FILE* g_disAsmFileCorDisTools;
#endif // USE_COREDISTOOLS

// Define DISASM_DEBUG to get verbose output of late disassembler inner workings.
// #define DISASM_DEBUG
#ifdef DISASM_DEBUG
#ifdef DEBUG
#define DISASM_DUMP(...)                                                                                               \
    if (VERBOSE)                                                                                                       \
    printf(__VA_ARGS__)
#else // !DEBUG
#define DISASM_DUMP(...) printf(__VA_ARGS__)
#endif // !DEBUG
#else  // !DISASM_DEBUG
#define DISASM_DUMP(...)
#endif // !DISASM_DEBUG

/*****************************************************************************/

#ifdef USE_MSVCDIS

#define MAX_CLASSNAME_LENGTH 1024

#if defined(HOST_AMD64)

#pragma comment(linker,                                                                                                \
                "/ALTERNATENAME:__imp_?CchFormatAddr@DIS@@QEBA_K_KPEAG0@Z=__imp_?CchFormatAddr@DIS@@QEBA_K_KPEA_W0@Z")
#pragma comment(linker,                                                                                                \
                "/ALTERNATENAME:__imp_?CchFormatInstr@DIS@@QEBA_KPEAG_K@Z=__imp_?CchFormatInstr@DIS@@QEBA_KPEA_W_K@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchaddrSet@DIS@@QEAAP6A_KPEBV1@_KPEAG1PEA_K@ZP6A_K01213@Z@Z=__imp_?PfncchaddrSet@DIS@@QEAAP6A_KPEBV1@_KPEA_W1PEA_K@ZP6A_K01213@Z@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchregSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@PEAG_K@ZP6A_K0123@Z@Z=__imp_?PfncchregSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@PEA_W_K@ZP6A_K0123@Z@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchregrelSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@KPEAG_KPEAK@ZP6A_K01K234@Z@Z=__imp_?PfncchregrelSet@DIS@@QEAAP6A_KPEBV1@W4REGA@1@KPEA_W_KPEAK@ZP6A_K01K234@Z@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchfixupSet@DIS@@QEAAP6A_KPEBV1@_K1PEAG1PEA_K@ZP6A_K011213@Z@Z=__imp_?PfncchfixupSet@DIS@@QEAAP6A_KPEBV1@_K1PEA_W1PEA_K@ZP6A_K011213@Z@Z")

#elif defined(HOST_X86)

#pragma comment(linker, "/ALTERNATENAME:__imp_?CchFormatAddr@DIS@@QBEI_KPAGI@Z=__imp_?CchFormatAddr@DIS@@QBEI_KPA_WI@Z")
#pragma comment(linker, "/ALTERNATENAME:__imp_?CchFormatInstr@DIS@@QBEIPAGI@Z=__imp_?CchFormatInstr@DIS@@QBEIPA_WI@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchaddrSet@DIS@@QAEP6GIPBV1@_KPAGIPA_K@ZP6GI012I3@Z@Z=__imp_?PfncchaddrSet@DIS@@QAEP6GIPBV1@_KPA_WIPA_K@ZP6GI012I3@Z@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchregSet@DIS@@QAEP6GIPBV1@W4REGA@1@PAGI@ZP6GI012I@Z@Z=__imp_?PfncchregSet@DIS@@QAEP6GIPBV1@W4REGA@1@PA_WI@ZP6GI012I@Z@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchregrelSet@DIS@@QAEP6GIPBV1@W4REGA@1@KPAGIPAK@ZP6GI01K2I3@Z@Z=__imp_?PfncchregrelSet@DIS@@QAEP6GIPBV1@W4REGA@1@KPA_WIPAK@ZP6GI01K2I3@Z@Z")
#pragma comment(                                                                                                       \
    linker,                                                                                                            \
    "/ALTERNATENAME:__imp_?PfncchfixupSet@DIS@@QAEP6GIPBV1@_KIPAGIPA_K@ZP6GI01I2I3@Z@Z=__imp_?PfncchfixupSet@DIS@@QAEP6GIPBV1@_KIPA_WIPA_K@ZP6GI01I2I3@Z@Z")

#endif // HOST_*

#endif // USE_MSVCDIS

/*****************************************************************************
 * Given an absolute address from the beginning of the code
 * find the corresponding emitter block and the relative offset
 * of the current address in that block
 * Was used to get to the fixup list of each block. The new emitter has
 * no such fixups. Something needs to be added for this.
 */

// These structs were defined in emit.h. Fake them here so DisAsm.cpp can compile

typedef struct codeFix
{
    codeFix* cfNext;
    unsigned cfFixup;
}* codeFixPtr;

typedef struct codeBlk
{
    codeFix* cbFixupLst;
}* codeBlkPtr;

#ifdef USE_MSVCDIS

/*****************************************************************************
 * The following is the callback for jump label and direct function calls fixups.
 * "addr" represents the address of jump that has to be
 * replaced with a label or function name.
 *
 * Return 1 if a name was written representing the address, 0 otherwise.
 */

/* static */
size_t __stdcall DisAssembler::disCchAddr(
    const DIS* pdis, DIS::ADDR addr, _In_reads_(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp)
{
    DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient();
    assert(pDisAsm);
    return pDisAsm->disCchAddrMember(pdis, addr, wz, cchMax, pdwDisp);
}

size_t DisAssembler::disCchAddrMember(
    const DIS* pdis, DIS::ADDR addr, _In_reads_(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp)
{
    /* First check the termination type of the instruction
     * because this might be a helper or static function call
     * check to see if we have a fixup for the current address */

    size_t retval = 0; // assume we don't know

#if defined(TARGET_XARCH)

    DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta());

    DISASM_DUMP("AddrMember %p (%p), termType %u\n", addr, disGetLinearAddr((size_t)addr), terminationType);

    switch (terminationType)
    {
            // int disCallSize;

        case DISX86::trmtaJmpShort:
        case DISX86::trmtaJmpCcShort:

            /* We have a short jump in the current code block - generate the label to which we jump */

            assert(0 <= disTarget && disTarget < disTotalCodeSize);
            swprintf_s(wz, cchMax, W("short L_%02u"), disLabels[disTarget]);
            retval = 1;
            break;

        case DISX86::trmtaJmpNear:
        case DISX86::trmtaJmpCcNear:

            /* We have a near jump. Check if is in the current code block.
             * Otherwise we have no target for it. */

            if (0 <= disTarget && disTarget < disTotalCodeSize)
            {
                swprintf_s(wz, cchMax, W("L_%02u"), disLabels[disTarget]);
                retval = 1;
            }
            break;

        case DISX86::trmtaCallNear16:
        case DISX86::trmtaCallNear32:

            /* check for local calls (i.e. CALL label) */

            if (0 <= disTarget && disTarget < disTotalCodeSize)
            {
                /* not a "call ds:[0000]" - go ahead */
                /* disTarget within block boundary -> local call */

                swprintf_s(wz, cchMax, W("short L_%02u"), disLabels[disTarget]);
                retval = 1;
                break;
            }

            /* this is a near call - in our case usually VM helper functions */

            /* find the emitter block and the offset of the call fixup */
            /* for the fixup offset we have to add the opcode size for the call - in the case of a near call is 1 */

            // disCallSize = 1;

            {
                size_t      absoluteTarget = (size_t)disGetLinearAddr(disTarget);
                const char* name           = disGetMethodFullName(absoluteTarget);
                if (name != nullptr)
                {
                    swprintf_s(wz, cchMax, W("%zx %S"), dspAddr(absoluteTarget), name);
                    retval = 1;
                    break;
                }
            }

            break;

#ifdef TARGET_AMD64

        case DISX86::trmtaFallThrough:

            /* memory indirect case. Could be for an LEA for the base address of a switch table, which is an arbitrary
             * address, currently of the first block after the prolog. */

            /* find the emitter block and the offset for the fixup
             * "addr" is the address of the immediate */

            break;

#endif // TARGET_AMD64

        default:

            printf("Termination type is %d\n", (int)terminationType);
            assert(!"treat this case\n");
            break;
    }

#elif defined(TARGET_ARM64)

    DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta());

    DISASM_DUMP("AddrMember %p (%p), termType %u\n", addr, disGetLinearAddr((size_t)addr), terminationType);

    switch (terminationType)
    {
            // int disCallSize;

        case DISARM64::TRMTA::trmtaBra:
        case DISARM64::TRMTA::trmtaBraCase:
        case DISARM64::TRMTA::trmtaBraCc:
        case DISARM64::TRMTA::trmtaBraCcCase:
        case DISARM64::TRMTA::trmtaBraCcInd:
        case DISARM64::TRMTA::trmtaBraInd:

            /* We have a jump. Check if is in the current code block.
             * Otherwise we have no target for it. */

            if (0 <= disTarget && disTarget < disTotalCodeSize)
            {
                swprintf_s(wz, cchMax, W("L_%02u"), disLabels[disTarget]);
                retval = 1;
            }
            break;

        case DISARM64::trmtaCall:
        case DISARM64::trmtaCallCc:
        case DISARM64::trmtaCallCcInd:
        case DISARM64::trmtaCallInd:

            /* check for local calls (i.e. CALL label) */

            if (0 <= disTarget && disTarget < disTotalCodeSize)
            {
                /* not a "call [0000]" - go ahead */
                /* disTarget within block boundary -> local call */

                swprintf_s(wz, cchMax, W("L_%02u"), disLabels[disTarget]);
                retval = 1;
                break;
            }

            /* this is a near call - in our case usually VM helper functions */

            /* find the emitter block and the offset of the call fixup */
            /* for the fixup offset we have to add the opcode size for the call - in the case of a near call is 1 */

            // disCallSize = 1;

            {
                size_t      absoluteTarget = (size_t)disGetLinearAddr(disTarget);
                const char* name           = disGetMethodFullName(absoluteTarget);
                if (name != nullptr)
                {
                    swprintf_s(wz, cchMax, W("%zx %S"), dspAddr(absoluteTarget), name);
                    retval = 1;
                    break;
                }
            }

            break;

        case DISARM64::trmtaFallThrough:

            /* memory indirect case. Could be for an LEA for the base address of a switch table, which is an arbitrary
             * address, currently of the first block after the prolog. */

            /* find the emitter block and the offset for the fixup
             * "addr" is the address of the immediate */

            {
                DIS::INSTRUCTION instr;
                DIS::OPERAND     ops[DISARM64::coperandMax];
                bool             ok = pdis->FDecode(&instr, ops, ArrLen(ops));
                if (ok)
                {
                    bool isAddress = false;
                    switch ((DISARM64::OPA)instr.opa)
                    {
                        case DISARM64::opaAdr:
                        case DISARM64::opaAdrp:
                            isAddress = true;
                            break;
                        default:
                            break;
                    }

                    if (isAddress && 0 <= addr && addr < disTotalCodeSize)
                    {
                        swprintf_s(wz, cchMax, W("L_%02u"), disLabels[addr]);
                        retval = 1;
                    }
                }
            }
            break;

        default:

            printf("Termination type is %d\n", (int)terminationType);
            assert(!"treat this case\n");
            break;
    }

#else // TARGET*
#error Unsupported or unset target architecture
#endif // TARGET*

    if (retval == 0)
    {
        if (disDiffable)
        {
            swprintf_s(wz, cchMax, W("%p"), dspAddr((void*)1));
        }
    }
    else
    {
        /* no displacement */

        *pdwDisp = 0x0;
    }

    return retval;
}

/*****************************************************************************
 * We annotate some instructions to get info needed to display the symbols
 * for that instruction.
 *
 * Return 1 if a name was written representing the address, 0 otherwise.
 */

/* static */
size_t __stdcall DisAssembler::disCchFixup(
    const DIS* pdis, DIS::ADDR addr, size_t size, _In_reads_(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp)
{
    DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient();
    assert(pDisAsm);

    return pDisAsm->disCchFixupMember(pdis, addr, size, wz, cchMax, pdwDisp);
}

size_t DisAssembler::disCchFixupMember(
    const DIS* pdis, DIS::ADDR addr, size_t size, _In_reads_(cchMax) wchar_t* wz, size_t cchMax, DWORDLONG* pdwDisp)
{
#if defined(TARGET_XARCH)

    DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta());
    // DIS::ADDR disIndAddr;

    DISASM_DUMP("FixupMember %016I64X (%08IX), size %d, termType %u\n", addr, disGetLinearAddr((size_t)addr), size,
                terminationType);

    // Is there a relocation registered for the address?

    size_t absoluteAddr = (size_t)disGetLinearAddr((size_t)addr);
    size_t targetAddr;
    bool   anyReloc = GetRelocationMap()->Lookup(absoluteAddr, &targetAddr);

    switch (terminationType)
    {
        DIS::ADDR disCallSize;

        case DISX86::trmtaFallThrough:

            /* memory indirect case */

            assert(addr > pdis->Addr());

            /* find the emitter block and the offset for the fixup
             * "addr" is the address of the immediate */

            if (anyReloc)
            {
                // Make instructions like "mov rcx, 7FE8247A638h" diffable.
                swprintf_s(wz, cchMax, W("%zXh"), dspAddr(targetAddr));
                break;
            }

            return 0;

        case DISX86::trmtaJmpInd:

            /* pretty rare case - something like "jmp [eax*4]"
             * not a function call or anything worth annotating */

            return 0;

        case DISX86::trmtaTrap:
        case DISX86::trmtaTrapCc:

            /* some instructions like division have a TRAP termination type - ignore it */

            return 0;

        case DISX86::trmtaJmpShort:
        case DISX86::trmtaJmpCcShort:

        case DISX86::trmtaJmpNear:
        case DISX86::trmtaJmpCcNear:

            /* these are treated by the CchAddr callback - skip them */

            return 0;

        case DISX86::trmtaCallNear16:
        case DISX86::trmtaCallNear32:

            if (anyReloc)
            {
                const char* name = disGetMethodFullName(targetAddr);
                if (name != nullptr)
                {
                    swprintf_s(wz, cchMax, W("%zx %S"), dspAddr(targetAddr), name);
                    break;
                }
            }

            /* these are treated by the CchAddr callback - skip them */

            return 0;

        case DISX86::trmtaCallInd:

            /* here we have an indirect call - find the indirect address */

            // BYTE * code = disGetLinearAddr((size_t)addr);
            // disIndAddr = (DIS::ADDR) (code+0);

            /* find the size of the call opcode - less the immediate */
            /* for the fixup offset we have to add the opcode size for the call */
            /* addr is the address of the immediate, pdis->Addr() returns the address of the disassembled instruction */

            assert(addr > pdis->Addr());
            disCallSize = addr - pdis->Addr();

            /* find the emitter block and the offset of the call fixup */

            return 0;

        default:

            printf("Termination type is %d\n", (int)terminationType);
            assert(!"treat this case\n");
            break;
    }

#elif defined(TARGET_ARM64)

    DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta());
    // DIS::ADDR disIndAddr;

    DISASM_DUMP("FixupMember %016I64X (%08IX), size %d, termType %u\n", addr, disGetLinearAddr((size_t)addr), size,
                terminationType);

    // Is there a relocation registered for the address?

    size_t absoluteAddr = (size_t)disGetLinearAddr((size_t)addr);
    size_t targetAddr;
    bool   anyReloc = GetRelocationMap()->Lookup(absoluteAddr, &targetAddr);

    switch (terminationType)
    {
        DIS::ADDR disCallSize;

        case DISARM64::TRMTA::trmtaUnknown:
            return 0;

        case DISARM64::TRMTA::trmtaFallThrough:

            if (anyReloc)
            {
                /* memory indirect case */

                assert(addr > pdis->Addr());

                /* find the emitter block and the offset for the fixup
                 * "addr" is the address of the immediate */

                // Make instructions like "mov rcx, 7FE8247A638h" diffable.
                swprintf_s(wz, cchMax, W("%zXh"), dspAddr(targetAddr));
                break;
            }

            return 0;

        case DISARM64::TRMTA::trmtaBraInd:
        case DISARM64::TRMTA::trmtaBraCcInd:

            /* pretty rare case - something like "jmp [eax*4]"
             * not a function call or anything worth annotating */

            return 0;

        case DISARM64::TRMTA::trmtaTrap:
        case DISARM64::TRMTA::trmtaTrapCc:

            /* some instructions like division have a TRAP termination type - ignore it */

            return 0;

        case DISARM64::TRMTA::trmtaBra:
        case DISARM64::TRMTA::trmtaBraCase:
        case DISARM64::TRMTA::trmtaBraCc:
        case DISARM64::TRMTA::trmtaBraCcCase:

            /* these are treated by the CchAddr callback - skip them */

            return 0;

        case DISARM64::TRMTA::trmtaCall:
        case DISARM64::TRMTA::trmtaCallCc:

            if (anyReloc)
            {
                const char* name = disGetMethodFullName(targetAddr);
                if (name != nullptr)
                {
                    swprintf_s(wz, cchMax, W("%zx %S"), dspAddr(targetAddr), name);
                    break;
                }
            }

            /* these are treated by the CchAddr callback - skip them */

            return 0;

        case DISARM64::TRMTA::trmtaCallInd:
        case DISARM64::TRMTA::trmtaCallCcInd:

            /* here we have an indirect call - find the indirect address */

            // BYTE * code = disGetLinearAddr((size_t)addr);
            // disIndAddr = (DIS::ADDR) (code+0);

            /* find the size of the call opcode - less the immediate */
            /* for the fixup offset we have to add the opcode size for the call */
            /* addr is the address of the immediate, pdis->Addr() returns the address of the disassembled instruction */

            assert(addr > pdis->Addr());
            disCallSize = addr - pdis->Addr();

            /* find the emitter block and the offset of the call fixup */

            return 0;

        default:

            printf("Termination type is %d\n", (int)terminationType);
            assert(!"treat this case\n");
            break;
    }

#else // TARGET*
#error Unsupported or unset target architecture
#endif // TARGET*

    /* no displacement */

    *pdwDisp = 0x0;

    return 1;
}

/*****************************************************************************
 * This the callback for register-relative operands in an instruction.
 * If the register is ESP or EBP, the operand may be a local variable
 * or a parameter, else the operand may be an instance variable
 *
 * Return 1 if a name was written representing the register-relative operand, 0 otherwise.
 */

/* static */
size_t __stdcall DisAssembler::disCchRegRel(
    const DIS* pdis, DIS::REGA reg, DWORD disp, _In_reads_(cchMax) wchar_t* wz, size_t cchMax, DWORD* pdwDisp)
{
    DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient();
    assert(pDisAsm);

    return pDisAsm->disCchRegRelMember(pdis, reg, disp, wz, cchMax, pdwDisp);
}

size_t DisAssembler::disCchRegRelMember(
    const DIS* pdis, DIS::REGA reg, DWORD disp, _In_reads_(cchMax) wchar_t* wz, size_t cchMax, DWORD* pdwDisp)
{
#if defined(TARGET_XARCH)

    DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta());
    // DIS::ADDR disIndAddr;

    DISASM_DUMP("RegRelMember reg %u, disp %u, termType %u\n", reg, disp, terminationType);

    switch (terminationType)
    {
        int         disOpcodeSize;
        const char* var;

        case DISX86::trmtaFallThrough:

            /* some instructions like division have a TRAP termination type - ignore it */

        case DISX86::trmtaTrap:
        case DISX86::trmtaTrapCc:

            var = disComp->codeGen->siStackVarName((size_t)(pdis->Addr() - disStartAddr), pdis->Cb(), reg, disp);
            if (var)
            {
                swprintf_s(wz, cchMax, W("%hs+%Xh '%hs'"), getRegName(reg), disp, var);
                *pdwDisp = 0;

                return 1;
            }

            /* This case consists of non-static members */

            /* find the emitter block and the offset for the fixup
             * fixup is emitted after the coding of the instruction - size = word (2 bytes)
             * GRRRR!!! - for the 16 bit case we have to check for the address size prefix = 0x66
             */

            if (*disGetLinearAddr(disCurOffset) == 0x66)
            {
                disOpcodeSize = 3;
            }
            else
            {
                disOpcodeSize = 2;
            }

            return 0;

        case DISX86::trmtaCallNear16:
        case DISX86::trmtaCallNear32:
        case DISX86::trmtaJmpInd:

            break;

        case DISX86::trmtaCallInd:

            /* check if this is a one byte displacement */

            if ((signed char)disp == (int)disp)
            {
                /* we have a one byte displacement -> there were no previous callbacks */

                /* find the size of the call opcode - less the immediate */
                /* this is a call R/M indirect -> opcode size is 2 */

                disOpcodeSize = 2;

                /* find the emitter block and the offset of the call fixup */

                return 0;
            }
            else
            {
                /* check if we already have a symbol name as replacement */

                if (disHasName)
                {
                    /* CchFixup has been called before - we have a symbol name saved in global var disFuncTempBuf */

                    swprintf_s(wz, cchMax, W("%hs+%u '%hs'"), getRegName(reg), disp, disFuncTempBuf);
                    *pdwDisp   = 0;
                    disHasName = false;
                    return 1;
                }
                else
                {
                    return 0;
                }
            }

        default:

            printf("Termination type is %d\n", (int)terminationType);
            assert(!"treat this case\n");

            break;
    }

#elif defined(TARGET_ARM64)

    DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta());

    DISASM_DUMP("RegRelMember reg %u, disp %u, termType %u\n", reg, disp, terminationType);

    switch (terminationType)
    {
        int         disOpcodeSize;
        const char* var;

        case DISARM64::TRMTA::trmtaFallThrough:

            /* some instructions like division have a TRAP termination type - ignore it */

        case DISARM64::TRMTA::trmtaTrap:
        case DISARM64::TRMTA::trmtaTrapCc:

            var = disComp->codeGen->siStackVarName((size_t)(pdis->Addr() - disStartAddr), pdis->Cb(), reg, disp);
            if (var)
            {
                swprintf_s(wz, cchMax, W("%hs+%Xh '%hs'"), getRegName(reg), disp, var);
                *pdwDisp = 0;

                return 1;
            }

            /* This case consists of non-static members */

            // TODO-ARM64-Bug?: Is this correct?
            disOpcodeSize = 2;
            return 0;

        case DISARM64::TRMTA::trmtaCall:
        case DISARM64::TRMTA::trmtaCallCc:
        case DISARM64::TRMTA::trmtaBraInd:
        case DISARM64::TRMTA::trmtaBraCcInd:
            break;

        case DISARM64::TRMTA::trmtaCallInd:
        case DISARM64::TRMTA::trmtaCallCcInd:

            /* check if this is a one byte displacement */

            if ((signed char)disp == (int)disp)
            {
                /* we have a one byte displacement -> there were no previous callbacks */

                /* find the size of the call opcode - less the immediate */
                /* this is a call R/M indirect -> opcode size is 2 */

                // TODO-ARM64-Bug?: Is this correct?
                disOpcodeSize = 2;

                /* find the emitter block and the offset of the call fixup */

                return 0;
            }
            else
            {
                /* check if we already have a symbol name as replacement */

                if (disHasName)
                {
                    /* CchFixup has been called before - we have a symbol name saved in global var disFuncTempBuf */

                    swprintf_s(wz, cchMax, W("%hs+%u '%hs'"), getRegName(reg), disp, disFuncTempBuf);
                    *pdwDisp   = 0;
                    disHasName = false;
                    return 1;
                }
                else
                {
                    return 0;
                }
            }

        default:

            printf("Termination type is %d\n", (int)terminationType);
            assert(!"treat this case\n");

            break;
    }

#else // TARGET*
#error Unsupported or unset target architecture
#endif // TARGET*

    /* save displacement */

    *pdwDisp = disp;

    return 1;
}

/*****************************************************************************
 *
 * Callback for register operands. Most probably, this is a local variable or
 * a parameter
 *
 * Return 1 if a name was written representing the register, 0 otherwise.
 */

/* static */
size_t __stdcall DisAssembler::disCchReg(const DIS* pdis, DIS::REGA reg, _In_reads_(cchMax) wchar_t* wz, size_t cchMax)
{
    DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient();
    assert(pDisAsm);

    return pDisAsm->disCchRegMember(pdis, reg, wz, cchMax);
}

size_t DisAssembler::disCchRegMember(const DIS* pdis, DIS::REGA reg, _In_reads_(cchMax) wchar_t* wz, size_t cchMax)
{
    // TODO-Review: DIS::REGA does not directly map to our regNumber! E.g., look at DISARM64::REGA --
    // the Wt registers come first (and do map to our regNumber), but the Xt registers follow.
    // Until this is fixed, don't use this function!
    disHasName = false;
    return 0;

#if 0
    const char * var = disComp->codeGen->siRegVarName(
                                            (size_t)(pdis->Addr() - disStartAddr),
                                            pdis->Cb(),
                                            reg);

    if (var)
    {
        if (disHasName)
        {
            /* CchRegRel has been called before - we have a symbol name saved in global var disFuncTempBuf */

            swprintf_s(wz, cchMax, W("%hs'%hs.%hs'"), getRegName(reg), var, disFuncTempBuf);
            disHasName = false;
            return 1;
        }
        else
        {
            swprintf_s(wz, cchMax, W("%hs'%hs'"), getRegName(reg), var);
            return 1;
        }
    }
    else
    {
        if (disHasName)
        {
            /* this is the ugly case when a variable is incorrectly presumed dead */

            swprintf_s(wz, cchMax, W("%hs'%hs.%hs'"), getRegName(reg), "<InstVar>", disFuncTempBuf);
            disHasName = false;
            return 1;
        }

        /* just to make sure we didn't bungle if var returns NULL */
        disHasName = false;
        return 0;
    }
#endif // 0
}

#endif // USE_MSVCDIS

/*****************************************************************************
 * Helper function to lazily create a map from code address to CORINFO_METHOD_HANDLE.
 */
AddrToMethodHandleMap* DisAssembler::GetAddrToMethodHandleMap()
{
    if (disAddrToMethodHandleMap == nullptr)
    {
        disAddrToMethodHandleMap = new (disComp->getAllocator()) AddrToMethodHandleMap(disComp->getAllocator());
    }
    return disAddrToMethodHandleMap;
}

/*****************************************************************************
 * Helper function to lazily create a map from code address to CORINFO_METHOD_HANDLE.
 */
AddrToMethodHandleMap* DisAssembler::GetHelperAddrToMethodHandleMap()
{
    if (disHelperAddrToMethodHandleMap == nullptr)
    {
        disHelperAddrToMethodHandleMap = new (disComp->getAllocator()) AddrToMethodHandleMap(disComp->getAllocator());
    }
    return disHelperAddrToMethodHandleMap;
}

/*****************************************************************************
 * Helper function to lazily create a map from relocation address to relocation target address.
 */
AddrToAddrMap* DisAssembler::GetRelocationMap()
{
    if (disRelocationMap == nullptr)
    {
        disRelocationMap = new (disComp->getAllocator()) AddrToAddrMap(disComp->getAllocator());
    }
    return disRelocationMap;
}

#ifdef USE_MSVCDIS

/*****************************************************************************
 * Return the count of bytes disassembled.
 */

size_t DisAssembler::CbDisassemble(DIS*        pdis,
                                   size_t      offs,
                                   DIS::ADDR   addr,
                                   const BYTE* pb,
                                   size_t      cbMax,
                                   FILE*       pfile,
                                   bool        findLabels,
                                   bool        printit /* = false */,
                                   bool        dispOffs /* = false */,
                                   bool        dispCodeBytes /* = false */)
{
    assert(pdis);

    size_t cb = pdis->CbDisassemble(addr, pb, cbMax);

    if (cb == 0)
    {
        DISASM_DUMP("CbDisassemble offs %zu addr %llu\n", offs, addr);
        // assert(!"can't disassemble instruction!!!");
        fprintf(pfile, "MSVCDIS can't disassemble instruction @ offset %zu (0x%02zx)!!!\n", offs, offs);
#if defined(TARGET_ARM64)
        fprintf(pfile, "%08Xh\n", *(unsigned int*)pb);
        return 4;
#else
        fprintf(pfile, "%02Xh\n", *pb);
        return 1;
#endif
    }

#if defined(TARGET_ARM64)
    assert(cb == 4); // all instructions are 4 bytes!
#endif               // TARGET_ARM64

    /* remember current offset and instruction size */

    disCurOffset = (size_t)addr;
    disInstSize  = cb;

    /* Set the disTarget address */

    disTarget = (size_t)pdis->AddrTarget();

    if (findLabels)
    {
#if defined(TARGET_XARCH)
        DISX86::TRMTA terminationType = DISX86::TRMTA(pdis->Trmta());

        /* check the termination type of the instruction */

        switch (terminationType)
        {
            case DISX86::trmtaCallNear16:
            case DISX86::trmtaCallNear32:
            case DISX86::trmtaCallFar:

            {
                // Don't count addresses in the relocation table
                size_t targetAddr;
                size_t absoluteAddr =
                    (size_t)disGetLinearAddr((size_t)pdis->AddrAddress(1)); // Get the address in the instruction of the
                                                                            // call target address (the address the
                                                                            // reloc is applied to).
                if (GetRelocationMap()->Lookup(absoluteAddr, &targetAddr))
                {
                    break;
                }
            }

                FALLTHROUGH;

            case DISX86::trmtaJmpShort:
            case DISX86::trmtaJmpNear:
            case DISX86::trmtaJmpFar:
            case DISX86::trmtaJmpCcShort:
            case DISX86::trmtaJmpCcNear:

                /* a CALL is local iff the disTarget is within the block boundary */

                /* mark the jump label in the disTarget vector and return */

                if (disTarget != DIS::addrNil) // There seems to be an assumption that you can't branch to the first
                                               // address of the function (prolog).
                {
                    if (0 <= disTarget && disTarget < disTotalCodeSize)
                    {
                        /* we're OK, disTarget within block boundary */

                        disLabels[disTarget] = 1;
                    }
                }
                break;

            case DISX86::trmtaFallThrough:
                // We'd like to be able to get a label for code like "lea rcx, [4]" that we use for jump tables, but I
                // can't figure out how.
                break;

            default:

                /* jump is not in the current code block */
                break;

        } // end switch
#elif defined(TARGET_ARM64)
        DISARM64::TRMTA terminationType = DISARM64::TRMTA(pdis->Trmta());

        /* check the termination type of the instruction */

        switch (terminationType)
        {
            case DISARM64::TRMTA::trmtaCall:
            case DISARM64::TRMTA::trmtaCallCc:

            {
                // Don't count addresses in the relocation table
                size_t targetAddr;
                size_t absoluteAddr =
                    (size_t)disGetLinearAddr((size_t)pdis->AddrAddress(1)); // Get the address in the instruction of the
                                                                            // call target address (the address the
                                                                            // reloc is applied to).
                if (GetRelocationMap()->Lookup(absoluteAddr, &targetAddr))
                {
                    break;
                }
            }

                FALLTHROUGH;

            case DISARM64::TRMTA::trmtaBra:
            case DISARM64::TRMTA::trmtaBraCase:
            case DISARM64::TRMTA::trmtaBraCc:
            case DISARM64::TRMTA::trmtaBraCcCase:

                /* a CALL is local iff the disTarget is within the block boundary */

                /* mark the jump label in the disTarget vector and return */

                if (disTarget != DIS::addrNil) // There seems to be an assumption that you can't branch to the first
                                               // address of the function (prolog).
                {
                    if (0 <= disTarget && disTarget < disTotalCodeSize)
                    {
                        /* we're OK, disTarget within block boundary */

                        disLabels[disTarget] = 1;
                    }
                }
                break;

            case DISARM64::TRMTA::trmtaFallThrough:
            {
                DIS::INSTRUCTION instr;
                DIS::OPERAND     ops[DISARM64::coperandMax];
                bool             ok = pdis->FDecode(&instr, ops, ArrLen(ops));
                if (ok)
                {
                    switch ((DISARM64::OPA)instr.opa)
                    {
                        case DISARM64::opaAdr:
                        case DISARM64::opaAdrp:
                            // operand 1 is an address
                            assert(instr.coperand >= 2);
                            assert(ops[1].opcls == DIS::opclsImmediate);
                            assert(ops[1].imcls == DIS::imclsAddress);
                            disTarget = ops[1].dwl;
                            break;
                        default:
                            break;
                    }

                    if (0 <= disTarget && disTarget < disTotalCodeSize)
                    {
                        /* we're OK, disTarget within block boundary */

                        disLabels[disTarget] = 1;
                    }
                }
            }
            break;

            default:

                /* jump is not in the current code block */
                break;

        } // end switch
#else // TARGET*
#error Unsupported or unset target architecture
#endif // TARGET*

        return cb;
    } // end if

    /* check if we have a label here */

    if (printit)
    {
        if (disLabels[addr])
        {
            /* print the label and the offset */

            fprintf(pfile, "L_%02u:\n", disLabels[addr]);
        }
    }

    wchar_t wz[MAX_CLASSNAME_LENGTH];
    pdis->CchFormatInstr(wz, ArrLen(wz));

    if (printit)
    {
        if (dispOffs)
        {
            fprintf(pfile, "%03X", offs);
        }

#ifdef TARGET_ARM64
#define CCH_INDENT 8 // fixed sized instructions, always 8 characters
#elif defined(TARGET_AMD64)
#define CCH_INDENT 30 // large constants sometimes
#else
#define CCH_INDENT 24
#endif

        size_t cchIndent = CCH_INDENT;

        if (dispCodeBytes)
        {
            static size_t cchBytesMax = -1;

            if (cchBytesMax == -1)
            {
                cchBytesMax = pdis->CchFormatBytesMax();
            }

            wchar_t wzBytes[MAX_CLASSNAME_LENGTH];
            assert(cchBytesMax < MAX_CLASSNAME_LENGTH);

            size_t cchBytes = pdis->CchFormatBytes(wzBytes, ArrLen(wzBytes));

            if (cchBytes > CCH_INDENT)
            {
                // Truncate the bytes if they are too long

                static const wchar_t* ellipses    = W("...\0");
                const size_t          cchEllipses = 4;

                memcpy(&wzBytes[CCH_INDENT - cchEllipses], ellipses, cchEllipses * sizeof(wchar_t));

                cchBytes = CCH_INDENT;
            }

            fprintf(pfile, "  %S", wzBytes);
            cchIndent = CCH_INDENT - cchBytes;
        }

        // print the dis-assembled instruction

        fprintf(pfile, "%*c %S\n", cchIndent, ' ', wz);
    }

    return cb;
}

// TODO-Cleanup: this is currently unused, unreferenced.
size_t CbDisassembleWithBytes(DIS* pdis, DIS::ADDR addr, const BYTE* pb, size_t cbMax, FILE* pfile)
{
    assert(pdis);
    DisAssembler* pDisAsm = (DisAssembler*)pdis->PvClient();
    assert(pDisAsm);

    wchar_t wz[MAX_CLASSNAME_LENGTH];

    pdis->CchFormatAddr(addr, wz, ArrLen(wz));

    size_t cchIndent = (size_t)fprintf(pfile, "  %S: ", wz);

    size_t cb = pdis->CbDisassemble(addr, pb, cbMax);

    if (cb == 0)
    {
        fprintf(pfile, "%02Xh\n", *pb);
        return (1);
    }

    size_t cchBytesMax = pdis->CchFormatBytesMax();

    if (cchBytesMax > 18)
    {
        // Limit bytes coded to 18 characters

        cchBytesMax = 18;
    }

    wchar_t wzBytes[64];
    size_t  cchBytes = pdis->CchFormatBytes(wzBytes, ArrLen(wzBytes));

    wchar_t* pwzBytes;
    wchar_t* pwzNext;

    for (pwzBytes = wzBytes; pwzBytes != NULL; pwzBytes = pwzNext)
    {
        bool fFirst = (pwzBytes == wzBytes);

        cchBytes = u16_strlen(pwzBytes);

        if (cchBytes <= cchBytesMax)
        {
            pwzNext = NULL;
        }

        else
        {
            wchar_t ch            = pwzBytes[cchBytesMax];
            pwzBytes[cchBytesMax] = '\0';

            if (ch == W(' '))
            {
                pwzNext = pwzBytes + cchBytesMax + 1;
            }

            else
            {
                pwzNext = (WCHAR*)u16_strrchr(pwzBytes, W(' '));
                assert(pwzNext);

                pwzBytes[cchBytesMax] = ch;
                *pwzNext++            = '\0';
            }
        }

        if (fFirst)
        {
            pdis->CchFormatInstr(wz, ArrLen(wz));
            fprintf(pfile, "%-*S %S\n", cchBytesMax, pwzBytes, wz);
        }

        else
        {
            fprintf(pfile, "%*c%S\n", cchIndent, ' ', pwzBytes);
        }
    }

    return (cb);
}

void DisAssembler::DisasmBuffer(FILE* pfile, bool printit)
{
    DIS* pdis = NULL;

#ifdef TARGET_X86
    pdis = DIS::PdisNew(DIS::distX86);
#elif defined(TARGET_AMD64)
    pdis = DIS::PdisNew(DIS::distX8664);
#elif defined(TARGET_ARM64)
    pdis = DIS::PdisNew(DIS::distArm64);
#else // TARGET*
#error Unsupported or unset target architecture
#endif

    if (pdis == NULL)
    {
        assert(!"out of memory in disassembler?");
        return;
    }

#ifdef TARGET_64BIT
    pdis->SetAddr64(true);
#endif

    // Store a pointer to the DisAssembler so that the callback functions
    // can get to it.

    pdis->PvClientSet((void*)this);

    /* Calculate addresses */

    size_t    ibCur = 0;
    DIS::ADDR addr  = 0; // Always emit code with respect to a "0" base address.

    /* First walk the code to find all jump targets */

    while (ibCur < disTotalCodeSize)
    {
        size_t cb;

        cb = CbDisassemble(pdis, ibCur, addr + ibCur, disGetLinearAddr(ibCur), disGetBufferSize(ibCur), pfile,
                           true); // find labels

        // CbDisassemble returning > MAX_INT... give me a break.
        ibCur += cb;
    }

    /* reset the label counter and start assigning consecutive number labels to the label locations */

    BYTE label = 0;
    for (unsigned i = 0; i < disTotalCodeSize; i++)
    {
        if (disLabels[i] != 0)
        {
            disLabels[i] = ++label;
        }
    }

    /* Re-initialize addresses for disassemble phase */

    ibCur = 0;
    addr  = 0;

    // Set callbacks only if we are displaying it. Else, the scheduler has called it

    if (printit)
    {
        /* Set the callback functions for symbol lookup */

        pdis->PfncchaddrSet(disCchAddr);
        pdis->PfncchfixupSet(disCchFixup);
        pdis->PfncchregrelSet(disCchRegRel);
        pdis->PfncchregSet(disCchReg);
    }

    while (ibCur < disTotalCodeSize)
    {
        size_t cb;

        cb = CbDisassemble(pdis, ibCur, addr + ibCur, disGetLinearAddr(ibCur), disGetBufferSize(ibCur), pfile,
                           false, // find labels
                           printit,
                           !disDiffable, // display relative offset
#ifdef DEBUG
                           !disDiffable // Display code bytes?
#else
                           false // Display code bytes?
#endif
        );

        ibCur += (unsigned)cb;
    }

    delete pdis;
}

#endif // USE_MSVCDIS

/*****************************************************************************
 * Given a linear offset into the code, find a pointer to the actual code (either in the hot or cold section)
 *
 * Arguments:
 *      offset  - The linear offset into the code. It must point within the code.
 */

const BYTE* DisAssembler::disGetLinearAddr(size_t offset)
{
    if (offset < disHotCodeSize)
    {
        return (const BYTE*)disHotCodeBlock + offset;
    }
    else
    {
        return (const BYTE*)disColdCodeBlock + offset - disHotCodeSize;
    }
}

/*****************************************************************************
 * Given a linear offset into the code, determine how many bytes are remaining in the buffer.
 * This will only return the number of bytes left in either the hot or cold buffer. This is used
 * to avoid walking off the end of the buffer.
 *
 * Arguments:
 *      offset  - The linear offset into the code. It must point within the code.
 */

size_t DisAssembler::disGetBufferSize(size_t offset)
{
    if (offset < disHotCodeSize)
    {
        return disHotCodeSize - offset;
    }
    else
    {
        return disHotCodeSize + disColdCodeSize - offset;
    }
}

/*****************************************************************************
 * Get the function name for a given absolute address.
 */

const char* DisAssembler::disGetMethodFullName(size_t addr)
{
    CORINFO_METHOD_HANDLE res;

    // First check the JIT helper table: they're very common.
    if (GetHelperAddrToMethodHandleMap()->Lookup(addr, &res))
    {
        return disComp->eeGetMethodFullName(res);
    }

    // Next check the "normal" registered call targets
    if (GetAddrToMethodHandleMap()->Lookup(addr, &res))
    {
        return disComp->eeGetMethodFullName(res);
    }

    return nullptr;
}

/*****************************************************************************
 * Register a called function address as associated with a CORINFO_METHOD_HANDLE.
 *
 * Arguments:
 *      addr    - The absolute address of the target function.
 *      methHnd - The method handle associated with 'addr'.
 */

void DisAssembler::disSetMethod(size_t addr, CORINFO_METHOD_HANDLE methHnd)
{
    if (!disComp->opts.doLateDisasm)
    {
        return;
    }

    if (disComp->eeGetHelperNum(methHnd))
    {
        DISASM_DUMP("Helper function: %p => %p\n", addr, methHnd);
        GetHelperAddrToMethodHandleMap()->Set(addr, methHnd, AddrToMethodHandleMap::SetKind::Overwrite);
    }
    else
    {
        DISASM_DUMP("Function: %p => %p\n", addr, methHnd);
        GetAddrToMethodHandleMap()->Set(addr, methHnd, AddrToMethodHandleMap::SetKind::Overwrite);
    }
}

/*****************************************************************************
 * Register a relocation.
 *
 * Arguments:
 *      relocAddr   - The absolute address the relocation applies to.
 *      targetAddr  - The absolute address the relocation points to.
 */

void DisAssembler::disRecordRelocation(size_t relocAddr, size_t targetAddr)
{
    if (!disComp->opts.doLateDisasm)
    {
        return;
    }

    DISASM_DUMP("Relocation %p => %p\n", relocAddr, targetAddr);
    GetRelocationMap()->Set(relocAddr, targetAddr);
}

/*****************************************************************************
 *
 * Disassemble the code which has been generated
 */

void DisAssembler::disAsmCode(BYTE*  hotCodePtr,
                              BYTE*  hotCodePtrRW,
                              size_t hotCodeSize,
                              BYTE*  coldCodePtr,
                              BYTE*  coldCodePtrRW,
                              size_t coldCodeSize)
{
    if (!disComp->opts.doLateDisasm)
    {
        return;
    }

#ifdef USE_COREDISTOOLS
    InitCoredistoolsDisasm();
#endif // USE_COREDISTOOLS

#ifdef DEBUG
    // Should we make it diffable?
    disDiffable = disComp->opts.dspDiffable;
#else  // !DEBUG
    // NOTE: non-debug builds are always diffable!
    disDiffable = true;
#endif // !DEBUG

#ifdef DEBUG
    const char* fileName = JitConfig.JitLateDisasmTo();
    if (fileName != nullptr)
    {
        disAsmFile = fopen_utf8(fileName, "a+");
    }
#else  // !DEBUG
    // NOTE: non-DEBUG builds always use jitstdout currently!
    disAsmFile = jitstdout();
#endif // !DEBUG

    if (disAsmFile == nullptr)
    {
        disAsmFile = jitstdout();
    }

    // As this writes to a common file, this is not reentrant.

    assert(hotCodeSize > 0);
    if (coldCodeSize == 0)
    {
        fprintf(disAsmFile, "************************** %hs:%hs size 0x%04IX **************************\n\n",
                disCurClassName, disCurMethodName, hotCodeSize);

        fprintf(disAsmFile, "Base address : %ph (RW: %ph)\n", dspAddr(hotCodePtr), dspAddr(hotCodePtrRW));
    }
    else
    {
        fprintf(disAsmFile,
                "************************** %hs:%hs hot size 0x%04IX cold size 0x%04IX **************************\n\n",
                disCurClassName, disCurMethodName, hotCodeSize, coldCodeSize);

        fprintf(disAsmFile, "Hot  address : %ph (RW: %ph)\n", dspAddr(hotCodePtr), dspAddr(hotCodePtrRW));
        fprintf(disAsmFile, "Cold address : %ph (RW: %ph)\n", dspAddr(coldCodePtr), dspAddr(coldCodePtrRW));
    }

    disStartAddr     = 0;
    disHotCodeBlock  = (size_t)hotCodePtrRW;
    disHotCodeSize   = hotCodeSize;
    disColdCodeBlock = (size_t)coldCodePtrRW;
    disColdCodeSize  = coldCodeSize;

    disTotalCodeSize = disHotCodeSize + disColdCodeSize;

    disLabels = new (disComp, CMK_DebugOnly) BYTE[disTotalCodeSize]();

    DisasmBuffer(disAsmFile, /* printIt */ true);

    fprintf(disAsmFile, "\n");

    if (disAsmFile != jitstdout())
    {
        fclose(disAsmFile);
    }
    else
    {
        fflush(disAsmFile);
    }
}

/*****************************************************************************/
// This function is called for every method. Checks if we are supposed to disassemble
// the method, and where to send the disassembly output.

void DisAssembler::disOpenForLateDisAsm(const char* curMethodName, const char* curClassName, PCCOR_SIGNATURE sig)
{
    if (!disComp->opts.doLateDisasm)
    {
        return;
    }

    disCurMethodName = curMethodName;
    disCurClassName  = curClassName;
}

#ifdef USE_COREDISTOOLS

// Helper functions to print messages from CoreDisTools Library
// The file/linenumber information is from this helper itself,
// since we are only linking with the CoreDisTools library.

// Currently all loggers are the same
#define LOGGER(L)                                                                                                      \
    static void __cdecl CorDisToolsLog##L(const char* msg, ...)                                                        \
    {                                                                                                                  \
        va_list argList;                                                                                               \
        va_start(argList, msg);                                                                                        \
        vflogf(g_disAsmFileCorDisTools, msg, argList);                                                                 \
        va_end(argList);                                                                                               \
        fprintf(g_disAsmFileCorDisTools, "\n");                                                                        \
    }

LOGGER(VERBOSE)
LOGGER(ERROR)
LOGGER(WARNING)

const PrintControl CorPrinter = {CorDisToolsLogERROR, CorDisToolsLogWARNING, CorDisToolsLogVERBOSE,
                                 CorDisToolsLogVERBOSE};

LONG               DisAssembler::s_disCoreDisToolsLibraryInitializing   = 0; // 0 = not initializing; 1 = initializing
bool               DisAssembler::s_disCoreDisToolsLibraryInitialized    = false;
bool               DisAssembler::s_disCoreDisToolsLibraryLoadSuccessful = false;
NewDisasm_t*       DisAssembler::s_PtrNewDisasm                         = nullptr;
DumpInstruction_t* DisAssembler::s_PtrDumpInstruction                   = nullptr;
FinishDisasm_t*    DisAssembler::s_PtrFinishDisasm                      = nullptr;

// Load coredistools library and find the entrypoints.
//
// Returns true on success, false on failure.
//
bool DisAssembler::InitCoredistoolsLibrary()
{
    while (InterlockedCompareExchange(&s_disCoreDisToolsLibraryInitializing, 1, 0) != 0)
    {
        // Wait for initialization to be complete
    }

    if (s_disCoreDisToolsLibraryInitialized)
    {
        // We've already loaded the library. Was it successful?
        InterlockedExchange(&s_disCoreDisToolsLibraryInitializing, 0); // unlock initialization
        return s_disCoreDisToolsLibraryLoadSuccessful;
    }

    s_disCoreDisToolsLibraryInitialized    = true;  // we only initialize once, even if it fails.
    s_disCoreDisToolsLibraryLoadSuccessful = false; // assume the worst

    const WCHAR* coreDisToolsLibraryNameW = MAKEDLLNAME_W("coredistools");
    const CHAR*  coreDisToolsLibraryNameA = MAKEDLLNAME_A("coredistools");

    HMODULE hCoreDisToolsLib = NULL;

#ifdef TARGET_UNIX
    // Unix will require the full path to coredistools. Assume that the
    // location is next to the full path to the JIT.

    WCHAR coreCLRLoadedPath[MAX_LONGPATH];
    int   returnVal = ::GetModuleFileNameW(NULL, coreCLRLoadedPath, MAX_LONGPATH);

    WCHAR* ptr;
    WCHAR* coreDisToolsLibrary;

    if (returnVal == 0)
    {
        printf("GetModuleFileNameW failed (0x%08x)", ::GetLastError());
        goto FinishedInitializing;
    }

    ptr = (WCHAR*)u16_strrchr(coreCLRLoadedPath, '/');

    // Move past the / character.
    ptr = ptr + 1;

    ::wcscpy_s(ptr, &coreCLRLoadedPath[MAX_LONGPATH] - ptr, coreDisToolsLibraryNameW);
    coreDisToolsLibrary = coreCLRLoadedPath;
#else  // TARGET_UNIX
    const WCHAR* coreDisToolsLibrary = coreDisToolsLibraryNameW;
#endif // !TARGET_UNIX

    // We never call FreeLibrary because we don't unload it until the JIT itself is unloaded.
    hCoreDisToolsLib = ::LoadLibraryExW(coreDisToolsLibrary, NULL, 0);
    if (hCoreDisToolsLib == 0)
    {
        printf("LoadLibrary(%s) failed (0x%08x)", coreDisToolsLibraryNameA, ::GetLastError());
        goto FinishedInitializing;
    }

    s_PtrNewDisasm = (NewDisasm_t*)::GetProcAddress(hCoreDisToolsLib, "NewDisasm");
    if (s_PtrNewDisasm == nullptr)
    {
        printf("GetProcAddress 'NewDisasm' failed (0x%08x)", ::GetLastError());
        goto FinishedInitializing;
    }
    s_PtrDumpInstruction = (DumpInstruction_t*)::GetProcAddress(hCoreDisToolsLib, "DumpInstruction");
    if (s_PtrDumpInstruction == nullptr)
    {
        printf("GetProcAddress 'DumpInstruction' failed (0x%08x)", ::GetLastError());
        goto FinishedInitializing;
    }
    s_PtrFinishDisasm = (FinishDisasm_t*)::GetProcAddress(hCoreDisToolsLib, "FinishDisasm");
    if (s_PtrFinishDisasm == nullptr)
    {
        printf("GetProcAddress 'FinishDisasm' failed (0x%08x)", ::GetLastError());
        goto FinishedInitializing;
    }

    s_disCoreDisToolsLibraryLoadSuccessful = true; // We made it!

    // done initializing

FinishedInitializing:
    InterlockedExchange(&s_disCoreDisToolsLibraryInitializing, 0); // unlock initialization
    return s_disCoreDisToolsLibraryLoadSuccessful;
}

// Coredistools disassembler initialization.
//
// Returns true on success, false on failure.
//
bool DisAssembler::InitCoredistoolsDisasm()
{
    if (!InitCoredistoolsLibrary())
    {
        return false;
    }

    TargetArch coreDisTargetArchitecture = Target_Host;

#if defined(TARGET_ARM64)
    coreDisTargetArchitecture = Target_Arm64;
#elif defined(TARGET_ARM)
    coreDisTargetArchitecture = Target_Thumb;
#elif defined(TARGET_X86)
    coreDisTargetArchitecture = Target_X86;
#elif defined(TARGET_AMD64)
    coreDisTargetArchitecture = Target_X64;
#elif defined(TARGET_LOONGARCH64)
    coreDisTargetArchitecture = Target_LoongArch64;
#elif defined(TARGET_RISCV64)
    coreDisTargetArchitecture = Target_RiscV64;
#else
#error Unsupported target for LATE_DISASM with USE_COREDISTOOLS
#endif

    corDisasm = (*s_PtrNewDisasm)(coreDisTargetArchitecture, &CorPrinter);

    return true;
}

// Coredistools disassembler shutdown
//
void DisAssembler::DoneCoredistoolsDisasm()
{
    if (corDisasm == nullptr)
    {
        return;
    }

    (*s_PtrFinishDisasm)(corDisasm);
    corDisasm = nullptr;
}

void DisAssembler::DisasmBuffer(FILE* pfile, bool printit)
{
    g_disAsmFileCorDisTools = pfile;

    unsigned              errorCount    = 0;
    static const unsigned maxErrorCount = 50;

    auto disasmRegion = [&](size_t executionAddress, size_t blockAddress, size_t blockSize) {
        uint8_t* address       = (uint8_t*)executionAddress;
        uint8_t* codeBytes     = (uint8_t*)blockAddress;
        size_t   codeSizeBytes = blockSize;
        while (codeSizeBytes > 0)
        {
            size_t instrLen = (*s_PtrDumpInstruction)(corDisasm, address, codeBytes, codeSizeBytes);
            if (instrLen == 0)
            {
                // error
                ++errorCount;
#ifdef TARGET_ARM64
                // For arm64, with fixed-size instructions, try to recover by skipping one
                // instruction and continuing.
                if (codeSizeBytes >= 4)
                {
                    CorDisToolsLogERROR("%x: %02x %02x %02x %02x", address, codeBytes[0], codeBytes[1], codeBytes[2],
                                        codeBytes[3]);
                    address += 4;
                    codeBytes += 4;
                    codeSizeBytes -= 4;
                }
                if (errorCount < maxErrorCount)
                {
                    continue;
                }
                else
                {
                    CorDisToolsLogERROR("Too many failures");
                }
#endif // TARGET_ARM64
                break;
            }
            assert(instrLen <= codeSizeBytes);
            address += instrLen;
            codeBytes += instrLen;
            codeSizeBytes -= instrLen;
        }
    };

    disasmRegion(0, disHotCodeBlock, disHotCodeSize);
    disasmRegion(disHotCodeSize, disColdCodeBlock, disColdCodeSize);
}

#endif // USE_COREDISTOOLS

/*****************************************************************************/

void DisAssembler::disInit(Compiler* pComp)
{
    assert(pComp);
    disComp                        = pComp;
    disHasName                     = false;
    disLabels                      = nullptr;
    disAddrToMethodHandleMap       = nullptr;
    disHelperAddrToMethodHandleMap = nullptr;
    disRelocationMap               = nullptr;
    disDiffable                    = false;
    disAsmFile                     = nullptr;

#ifdef USE_COREDISTOOLS
    g_disAsmFileCorDisTools = nullptr;
    corDisasm               = nullptr;
#endif // USE_COREDISTOOLS
}

void DisAssembler::disDone()
{
#ifdef USE_COREDISTOOLS
    DoneCoredistoolsDisasm();
#endif // USE_COREDISTOOLS
}

/*****************************************************************************/
#endif // LATE_DISASM
/*****************************************************************************/
